 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.core.runtime.internal.stats;

 import java.io.*;
 import java.util.*;

 /**
  * Contains information about the classes and the bundles loaded by a given classloader. Typically there is one classloader per plugin so at levels above boot, this equates to information about
  * classes and bundles in a plugin.
  */
 public class ClassloaderStats {
     private String id;
     private long loadingTime; // time spent loading classes
 private int failureCount = 0; // number of classes requested but that we fail to provide
 /**
      * classes loaded by the plugin (key: class name, value: ClassStats)
      */
     private Map classes = Collections.synchronizedMap(new HashMap(20));
     private ArrayList bundles = new ArrayList(2); // bundles loaded

     private boolean keepTraces = false; // indicate whether or not the traces of classes loaded are kept

     // filters to indicate which classes we want to keep the traces
 private static ArrayList packageFilters = new ArrayList(4); // filters on a package basis
 private static Set pluginFilters = new HashSet(5); // filters on a plugin basis

     private static Hashtable classStacks = new Hashtable(); // represents the classes that are currently being loaded
 /**
      * a dictionary of the classloaderStats (key: pluginId, value: ClassloaderStats)
      */
     private static Map loaders = Collections.synchronizedMap(new HashMap(20));
     public static File traceFile;

     static {
         if (StatsManager.TRACE_CLASSES || StatsManager.TRACE_BUNDLES)
             initializeTraceOptions();
     }

     private static void initializeTraceOptions() {
         // create the trace file
 String filename = StatsManager.TRACE_FILENAME;
         traceFile = new File(filename);
         traceFile.delete();

         //load the filters
 if (!StatsManager.TRACE_CLASSES)
             return;
         filename = StatsManager.TRACE_FILTERS;
         if (filename == null || filename.length() == 0)
             return;
         try {
             File filterFile = new File(filename);
             System.out.print("Runtime tracing elements defined in: " + filterFile.getAbsolutePath() + "..."); //$NON-NLS-1$ //$NON-NLS-2$
 InputStream input = new FileInputStream(filterFile);
             System.out.println(" Loaded."); //$NON-NLS-1$
 Properties filters = new Properties() {
                 private static final long serialVersionUID = 3546359543853365296L;

                 public Object put(Object key, Object value) {
                     addFilters((String ) key, (String ) value);
                     return null;
                 }
             };
             try {
                 filters.load(input);
             } finally {
                 input.close();
             }
         } catch (IOException e) {
             System.out.println(" No trace filters loaded."); //$NON-NLS-1$
 }
     }

     protected static void addFilters(String key, String value) {
         String [] filters = StatsManager.getArrayFromList(value);
         if ("plugins".equals(key)) //$NON-NLS-1$
 pluginFilters.addAll(Arrays.asList(filters));
         if ("packages".equals(key)) //$NON-NLS-1$
 packageFilters.addAll(Arrays.asList(filters));
     }

     public static void startLoadingClass(String id, String className) {
         findLoader(id).startLoadClass(className);
     }

     // get and create if does not exist
 private static ClassloaderStats findLoader(String id) {
         synchronized (loaders) {
             ClassloaderStats result = (ClassloaderStats) loaders.get(id);
             if (result == null) {
                 result = new ClassloaderStats(id);
                 loaders.put(id, result);
             }
             return result;
         }
     }

     public static synchronized Stack getClassStack() {
         Stack result = (Stack) classStacks.get(Thread.currentThread());
         if (result == null) {
             result = new Stack();
             classStacks.put(Thread.currentThread(), result);
         }
         return result;
     }

     public static ClassloaderStats[] getLoaders() {
         //the parameter to toArray is of size zero for thread safety, otherwise this
 //could return an array with null entries if the map shrinks concurrently
 return (ClassloaderStats[]) loaders.values().toArray(new ClassloaderStats[0]);
     }

     public static void endLoadingClass(String id, String className, boolean success) {
         findLoader(id).endLoadClass(className, success);
     }

     public static void loadedBundle(String id, ResourceBundleStats info) {
         findLoader(id).loadedBundle(info);
     }

     public static ClassloaderStats getLoader(String id) {
         return (ClassloaderStats) loaders.get(id);
     }

     public ClassloaderStats(String id) {
         this.id = id;
         keepTraces = pluginFilters.contains(id);
     }

     public void addBaseClasses(String [] baseClasses) {
         for (int i = 0; i < baseClasses.length; i++) {
             String name = baseClasses[i];
             if (classes.get(name) == null) {
                 ClassStats value = new ClassStats(name, this);
                 value.toBaseClass();
                 classes.put(name, value);
             }
         }
     }

     private void loadedBundle(ResourceBundleStats bundle) {
         bundles.add(bundle);
     }

     public ArrayList getBundles() {
         return bundles;
     }

     private synchronized void startLoadClass(String name) {
         getClassStack().push(findClass(name));
     }

     // internal method that return the existing classStats or creates one
 private ClassStats findClass(String name) {
         ClassStats result = (ClassStats) classes.get(name);
         return result == null ? new ClassStats(name, this) : result;
     }

     private synchronized void endLoadClass(String name, boolean success) {
         ClassStats current = (ClassStats) getClassStack().pop();
         if (!success) {
             failureCount++;
             return;
         }
         if (current.getLoadOrder() >= 0)
             return;

         classes.put(name, current);
         current.setLoadOrder(classes.size());
         current.loadingDone();
         traceLoad(name, current);

         // is there something on the load stack. if so, link them together...
 Stack classStack = getClassStack();
         if (classStack.size() != 0) {
             // get the time spent loading cli and subtract its load time from the class that requires loading
 ClassStats previous = ((ClassStats) classStack.peek());
             previous.addTimeLoadingOthers(current.getTimeLoading());
             current.setLoadedBy(previous);
             previous.loaded(current);
         } else {
             loadingTime = loadingTime + current.getTimeLoading();
         }
     }

     private void traceLoad(String name, ClassStats target) {
         // Stack trace code
 if (!keepTraces) {
             boolean found = false;
             for (int i = 0; !found && i < packageFilters.size(); i++)
                 if (name.startsWith((String ) packageFilters.get(i)))
                     found = true;
             if (!found)
                 return;
         }

         // Write the stack trace. The position in the file are set to the corresponding classStat object
 try {
             target.setTraceStart(traceFile.length());
             PrintWriter output = new PrintWriter(new FileOutputStream(traceFile.getAbsolutePath(), true));
             try {
                 output.println("Loading class: " + name); //$NON-NLS-1$
 output.println("Class loading stack:"); //$NON-NLS-1$
 output.println("\t" + name); //$NON-NLS-1$
 Stack classStack = getClassStack();
                 for (int i = classStack.size() - 1; i >= 0; i--)
                     output.println("\t" + ((ClassStats) classStack.get(i)).getClassName()); //$NON-NLS-1$
 output.println("Stack trace:"); //$NON-NLS-1$
 new Throwable ().printStackTrace(output);
             } finally {
                 output.close();
             }
             target.setTraceEnd(traceFile.length());
         } catch (FileNotFoundException e) {
             e.printStackTrace();
         }
     }

     public int getClassLoadCount() {
         return classes.size();
     }

     public long getClassLoadTime() {
         return loadingTime;
     }

     public ClassStats[] getClasses() {
         //the parameter to toArray is of size zero for thread safety, otherwise this
 //could return an array with null entries if the map shrinks concurrently
 return (ClassStats[]) classes.values().toArray(new ClassStats[0]);
     }

     public String getId() {
         return id;
     }
 }

